package com.linking.redis.manager;

import cn.hutool.core.util.StrUtil;
import com.linking.config.pojo.SelfConfigBO.RedisConfigBO.RedisConfig;
import com.linking.redis.configuration.FastJsonRedisSerializer;
import java.time.Duration;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @Author YaoWeiXin
 * @Date 2020/4/13 19:52
 * @Description redis管理类
 */
@EqualsAndHashCode(callSuper = false)
@Data
public abstract class AbstractRedisManager extends CachingConfigurerSupport implements
    ApplicationContextAware {

  private final static ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
  private final String BEAN_PREFIX = "LinkingRedis-";
  private String beanPrimary = "";
  private AbstractApplicationContext ctx;

  public static String getCurName() {
    return THREAD_LOCAL.get();
  }

  public static void setCurName(String name) {
    THREAD_LOCAL.set(name);
  }

  public static void removeCurName() {
    THREAD_LOCAL.remove();
  }

  /**
   * 设置白名单---非常重要******** 使用fastJson的时候：序列化时将class信息写入，反解析的时候， fastJson默认情况下会开启autoType的检查，相当于一个白名单检查，
   * 如果序列化信息中的类路径不在autoType中， 反解析就会报com.alibaba.fastjson.JSONException: autoType is not support的异常
   */
  public abstract void addCacheAccept();

  @Override
  public void setApplicationContext(ApplicationContext ctx) throws BeansException {
    this.ctx = (AbstractApplicationContext) ctx;
  }

  @Bean
  @Primary
  public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    // 初始化一个RedisCacheWriter
    RedisCacheWriter redisCacheWriter = RedisCacheWriter
        .nonLockingRedisCacheWriter(connectionFactory);
    // 使用FastJson序列化
    FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(
        Object.class);
    RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair
        .fromSerializer(fastJsonRedisSerializer);
    RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
        .serializeValuesWith(pair);

    // 设置过期时间
    defaultCacheConfig = defaultCacheConfig.entryTtl(Duration.ofSeconds(100));
    // 初始化RedisCacheManager
    RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
    // 设置白名单---非常重要********
    addCacheAccept();
    return cacheManager;
  }


  /**
   * 设置 redis 数据默认过期时间 设置@cacheable 序列化方式
   */
  @Bean
  public RedisCacheConfiguration redisCacheConfiguration() {
    FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(
        Object.class);
    RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
    configuration = configuration.serializeValuesWith(
        RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))
        .entryTtl(
            Duration.ofDays(30));
    return configuration;
  }

  /**
   * 初始redis连接池
   *
   * @param jedisPoolConfig 连接池配置
   * @param redisConfigs    redis配置
   */
  protected void init(JedisPoolConfig jedisPoolConfig, List<RedisConfig> redisConfigs) {
    for (RedisConfig redisConfig : redisConfigs) {
      register(redisConfig.getName(),
          buildRedisTemplate(buildConnectionFactory(jedisPoolConfig, redisConfig)));
      if (StrUtil.isEmpty(beanPrimary)) {
        beanPrimary = redisConfig.getName();
      }
    }
  }

  private RedisTemplate buildRedisTemplate(RedisConnectionFactory connectionFactory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    //使用fastJson序列化
    FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
    // value值的序列化采用fastJsonRedisSerializer
    template.setValueSerializer(fastJsonRedisSerializer);
    template.setHashValueSerializer(fastJsonRedisSerializer);
    // key的序列化采用StringRedisSerializer
    template.setKeySerializer(new StringRedisSerializer());
    template.setHashKeySerializer(new StringRedisSerializer());
    template.setConnectionFactory(connectionFactory);
    return template;
  }

  /**
   * jedis连接工厂
   */
  private JedisConnectionFactory buildConnectionFactory(JedisPoolConfig jedisPoolConfig,
      RedisConfig redisConfig) {
    RedisStandaloneConfiguration redisStandaloneConfiguration =
        new RedisStandaloneConfiguration();
    // 设置redis服务器的host或者ip地址
    redisStandaloneConfiguration.setHostName(redisConfig.getHost());
    // 设置redis的服务的端口号
    redisStandaloneConfiguration.setPort(redisConfig.getPort());
    // 设置密码
    redisStandaloneConfiguration.setPassword(redisConfig.getPwd());
    // 设置默认使用的数据库
    redisStandaloneConfiguration.setDatabase(redisConfig.getDb());
    JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jedisBuilder = (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder) JedisClientConfiguration
        .builder();
    jedisBuilder.poolConfig(jedisPoolConfig);
    JedisClientConfiguration jedisClientConfiguration = jedisBuilder.build();
    return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration);
  }

  /**
   * 注册bean
   *
   * @param name          bean名字
   * @param redisTemplate redis操作类
   */
  private void register(String name, RedisTemplate redisTemplate) {
    String beanName = BEAN_PREFIX + name;
    DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) ctx.getBeanFactory();
    // BeanDefinitionBuilder构造过程
    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
        .genericBeanDefinition(redisTemplate.getClass());
    beanDefinitionBuilder
        .addPropertyValue("connectionFactory", redisTemplate.getConnectionFactory());
    beanDefinitionBuilder
        .addPropertyValue("valueSerializer", redisTemplate.getValueSerializer());
    beanDefinitionBuilder
        .addPropertyValue("hashValueSerializer", redisTemplate.getHashValueSerializer());
    beanDefinitionBuilder
        .addPropertyValue("keySerializer", redisTemplate.getKeySerializer());
    beanDefinitionBuilder
        .addPropertyValue("hashKeySerializer", redisTemplate.getHashKeySerializer());
    beanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
  }

  /**
   * 获取操作类
   *
   * @param name 连接名
   * @return 操作类
   */
  public RedisTemplate getTemplate(String name) {
    String beanName = BEAN_PREFIX + name;
    return (RedisTemplate) ctx.getBean(beanName);
  }
}
